﻿namespace DCHealthCheck {

    #region Usings
    using DCHealthCheck.Common;
    using DCHealthCheck.DomainModel;
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Management;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks; 
    #endregion

    public class SystemWmiInfoBuilder {

        #region Members
        private static log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public static readonly string OSWmiQueryFilter = "Select FreePhysicalMemory, TotalVisibleMemorySize, NumberOfProcesses, InstallDate, CurrentTimeZone, LocalDateTime, LastBootUpTime from Win32_OperatingSystem";

        private readonly SystemInfo SystemInfo;

        private static readonly TimeSpan TimeDifferenceThreshold = new TimeSpan(0, 4, 50);

        private static readonly TimeSpan WmiSearcherTimeout = new TimeSpan(0, 2, 0); 
        #endregion

        #region Constructor
        public SystemWmiInfoBuilder(SystemInfo systemInfo) {
            if (systemInfo == null) {
                throw new ArgumentNullException("systemInfo");
            }
            this.SystemInfo = systemInfo;
        } 
        #endregion

        #region Methods
        public void GetSystemWmiInfo() {

            #region  Win32_OperatingSystem info
            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.SystemInfo.DomainController.DNSName));
                var query = new ObjectQuery(OSWmiQueryFilter);
                using (var searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;
                    foreach (ManagementObject systemProperty in searcher.Get()) {
                        if (systemProperty["FreePhysicalMemory"] != null) this.SystemInfo.FreeMemoryMB = Convert.ToInt32(systemProperty["FreePhysicalMemory"]) / 1024;
                        if (systemProperty["TotalVisibleMemorySize"] != null) this.SystemInfo.TotalMemoryMB = Convert.ToInt32(systemProperty["TotalVisibleMemorySize"]) / 1024;
                        if (systemProperty["NumberOfProcesses"] != null) this.SystemInfo.ProcessCount = Convert.ToInt32(systemProperty["NumberOfProcesses"]);
                        if (systemProperty["CurrentTimeZone"] != null) this.SystemInfo.UTCOffset = Convert.ToInt32(systemProperty["CurrentTimeZone"]);

                        if (systemProperty["LastBootUpTime"] != null) {
                            var startupDateTime = DateTime.MinValue;
                            if (DateTime.TryParseExact(systemProperty["LastBootUpTime"].ToString().Substring(0, 14), "yyyyMMddHHmmss", CultureInfo.CurrentCulture.DateTimeFormat, DateTimeStyles.None, out startupDateTime)) {
                                this.SystemInfo.StartupDateTimeUTC = this.SystemInfo.UTCOffset != -1 ? startupDateTime.AddMinutes(-this.SystemInfo.UTCOffset) : startupDateTime;
                                this.SystemInfo.UptimeDays = DateTime.UtcNow.Subtract(this.SystemInfo.StartupDateTimeUTC).Days;
                            }
                        }

                        if (systemProperty["InstallDate"] != null) {
                            var osInstallDate = DateTime.MinValue;
                            DateTime.TryParseExact(systemProperty["InstallDate"].ToString().Substring(0, 14), "yyyyMMddHHmmss", CultureInfo.CurrentCulture.DateTimeFormat, DateTimeStyles.None, out osInstallDate);
                            this.SystemInfo.OSInstallDate = osInstallDate;
                        }

                        if (systemProperty["LocalDateTime"] != null) {
                            var dcTime = DateTime.MinValue;
                            if (DateTime.TryParseExact(systemProperty["LocalDateTime"].ToString().Substring(0, 14), "yyyyMMddHHmmss", CultureInfo.CurrentCulture.DateTimeFormat, DateTimeStyles.None, out dcTime)) {
                                this.SystemInfo.DCTime = dcTime;
                                this.SystemInfo.DCTimeUTC = this.SystemInfo.UTCOffset != -1 ? this.SystemInfo.DCTime.AddMinutes(-this.SystemInfo.UTCOffset) : this.SystemInfo.DCTime;
                                var healthCheckServerTimeUTC = DateTime.UtcNow;

                                if ((this.SystemInfo.UTCOffset != -1) && (this.SystemInfo.DCTimeUTC != DateTime.MinValue)) {
                                    TimeSpan timeDiff = healthCheckServerTimeUTC > this.SystemInfo.DCTimeUTC ?
                                        (healthCheckServerTimeUTC - this.SystemInfo.DCTimeUTC) :
                                        (this.SystemInfo.DCTimeUTC - healthCheckServerTimeUTC);

                                    this.SystemInfo.TimeDiff = timeDiff.ToString();

                                    if (TimeSpan.Parse(this.SystemInfo.TimeDiff) > TimeDifferenceThreshold) {
                                        this.SystemInfo.TimeSyncTest = TestResult.Fail;

                                        string message = string.Format("DC: {0} DomainController time (UTC): {1} Health Check Server reference time (UTC): {2} Time difference: {3} Exceeds threshold: {4}",
                                            this.SystemInfo.DomainController.DNSName, this.SystemInfo.DCTimeUTC.ToString("yyyy-MMM-dd HH:mm:ss"), 
                                            healthCheckServerTimeUTC.ToString("yyyy-MMM-dd HH:mm:ss"), this.SystemInfo.TimeDiff, 
                                            TimeDifferenceThreshold.ToString());

                                        Log.WarnFormat("[ThreadID: {0}] {1} {2}", Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), message);

                                        this.SystemInfo.ErrorMessageBuilder.AppendLine(string.Format(" -Warning: {0}", message));
                                        this.SystemInfo.ErrorMessageBuilder.AppendLine(string.Format("  -> Action required: Restart the Windows Time Service, wait two minutes, then use the following command to verify it is functioning correctly:"));
                                        this.SystemInfo.ErrorMessageBuilder.AppendLine(string.Format("  -> 'w32tm /query /computer:{0} /status /verbose'", this.SystemInfo.DomainController.DNSName));
                                    }
                                    else {
                                        this.SystemInfo.TimeSyncTest = TestResult.Success;
                                    }
                                } // if ((this.systemInfo.UTCOffset != -1) && (this.systemInfo.DCTimeUTC != sqlDateTimeMinValue))
                            } // if (DateTime.TryParseExact(systemProperty["LocalDateTime"].ToString().Substring(0, 14), "yyyyMMddHHmmss", CultureInfo.CurrentCulture.DateTimeFormat, DateTimeStyles.None, out dcTime))
                        } // if (systemProperty["LocalDateTime"] != null)

                        break;
                    } // foreach (ManagementObject systemProperty in searcher.Get())
                } // using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query))
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Getting  Win32_OperatingSystem WMI information: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.SystemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            #endregion

            #region Win32_ComputerSystem info
            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.SystemInfo.DomainController.DNSName));
                var query = new ObjectQuery("Select * from Win32_ComputerSystem");
                using (var searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;
                    foreach (ManagementObject computerProperty in searcher.Get()) {
                        if (computerProperty["DaylightInEffect"] != null) this.SystemInfo.DST = Convert.ToBoolean(computerProperty["DaylightInEffect"]);
                        if (computerProperty["Manufacturer"] != null) this.SystemInfo.Manufacturer = computerProperty["Manufacturer"].ToString();
                        if (computerProperty["Model"] != null) this.SystemInfo.Model = computerProperty["Model"].ToString();
                        if (computerProperty["NumberOfProcessors"] != null) this.SystemInfo.CPUCount = Convert.ToInt32(computerProperty["NumberOfProcessors"]);

                        if (!string.IsNullOrWhiteSpace(this.SystemInfo.DomainController.OSName) && !this.SystemInfo.DomainController.OSName.Contains("2003")) {
                            if (computerProperty["NumberOfLogicalProcessors"] != null) this.SystemInfo.LogicalCPUCount = Convert.ToInt32(computerProperty["NumberOfLogicalProcessors"]);
                        }

                        break;
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2}  Getting Win32_ComputerSystem WMI information: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.SystemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            #endregion

            #region Win32_ComputerSystemProduct info
            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.SystemInfo.DomainController.DNSName));
                var query = new ObjectQuery("Select IdentifyingNumber from Win32_ComputerSystemProduct");
                using (var searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;
                    foreach (ManagementObject computerProductProperty in searcher.Get()) {
                        if (computerProductProperty["IdentifyingNumber"] != null) this.SystemInfo.SerialNumber = computerProductProperty["IdentifyingNumber"].ToString();

                        break;
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Getting Win32_ComputerSystemProduct WMI information: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.SystemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            #endregion

            #region Win32_LogicalDisk info
            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.SystemInfo.DomainController.DNSName));
                var query = new ObjectQuery("Select Name, Size, FreeSpace from Win32_LogicalDisk");
                using (var searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;
                    foreach (ManagementObject diskProperty in searcher.Get()) {
                        if (diskProperty["Name"].ToString().Trim() == "C:") {
                            this.SystemInfo.CDriveTotalMB = (int)(Convert.ToInt64(diskProperty["Size"]) / 1048676);
                            this.SystemInfo.CDriveFreeMB = (int)(Convert.ToInt64(diskProperty["FreeSpace"]) / 1048676);

                            break;
                        }
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Getting Win32_LogicalDisk WMI information: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.SystemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            #endregion

            #region Win32_PerfFormattedData_Tcpip_TCPv4 info
            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.SystemInfo.DomainController.DNSName));
                var query = new ObjectQuery("Select ConnectionsEstablished from Win32_PerfFormattedData_Tcpip_TCPv4");
                using (var searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;
                    foreach (ManagementObject tcpProperty in searcher.Get()) {
                        if (tcpProperty["ConnectionsEstablished"] != null) this.SystemInfo.TcpConnections = Convert.ToInt32(tcpProperty["ConnectionsEstablished"]);
                        break;
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Getting Win32_PerfFormattedData_Tcpip_TCPv4 WMI information: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.SystemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            #endregion

            #region Win32_PerfFormattedData_PerfNet_Server info
            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.SystemInfo.DomainController.DNSName));
                var query = new ObjectQuery("Select LogonTotal from Win32_PerfFormattedData_PerfNet_Server");
                using (var searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;
                    foreach (ManagementObject serverProperty in searcher.Get()) {
                        if (serverProperty["LogonTotal"] != null) this.SystemInfo.TotalLogons = Convert.ToInt32(serverProperty["LogonTotal"]);
                        break;
                    }
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Getting Win32_PerfFormattedData_PerfNet_Server WMI information: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.SystemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            #endregion

            #region Win32_PerfFormattedData_DNS_DNS info
            if (!string.IsNullOrWhiteSpace(this.SystemInfo.DomainController.OSName) && !this.SystemInfo.DomainController.OSName.Contains("2003")) {
                try {
                    var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.SystemInfo.DomainController.DNSName));
                    var query = new ObjectQuery("Select TotalQueryReceived, TotalResponseSent, RecursiveQueries from Win32_PerfFormattedData_DNS_DNS");
                    using (var searcher = new ManagementObjectSearcher(scope, query)) {
                        searcher.Options.Timeout = WmiSearcherTimeout;
                        foreach (ManagementObject dnsProperty in searcher.Get()) {
                            if (dnsProperty["TotalQueryReceived"] != null) this.SystemInfo.DNSQueryReceived = Convert.ToInt32(dnsProperty["TotalQueryReceived"]);
                            if (dnsProperty["TotalResponseSent"] != null) this.SystemInfo.DNSResponseSent = Convert.ToInt32(dnsProperty["TotalResponseSent"]);
                            if (dnsProperty["RecursiveQueries"] != null) this.SystemInfo.DNSRecursiveQueries = Convert.ToInt32(dnsProperty["RecursiveQueries"]);

                            break;
                        }
                    }
                }
                catch (Exception e) {
                    Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Getting Win32_PerfFormattedData_DNS_DNS WMI information: {3}",
                        Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.SystemInfo.DomainController.DNSName, e.VerboseExceptionString());
                }
            }
            #endregion

            #region Win32_NetworkAdapterConfiguration info
            try {
                var scope = new ManagementScope(string.Format(@"\\{0}\root\CIMV2", this.SystemInfo.DomainController.DNSName));
                var query = new ObjectQuery("Select IPAddress from Win32_NetworkAdapterConfiguration WHERE IPEnabled=true");
                using (var searcher = new ManagementObjectSearcher(scope, query)) {
                    searcher.Options.Timeout = WmiSearcherTimeout;
                    foreach (ManagementObject networkAdapterProperty in searcher.Get()) {
                        if (networkAdapterProperty["IPAddress"] != null) {
                            foreach (string ipAddress in networkAdapterProperty["IPAddress"] as string[]) {
                                this.SystemInfo.IPAddresses += string.Format("{0},", ipAddress);
                            }
                        }

                        this.SystemInfo.IPAddresses += ",";
                    }

                    while (this.SystemInfo.IPAddresses.EndsWith(",")) {
                        this.SystemInfo.IPAddresses = this.SystemInfo.IPAddresses.Substring(0, this.SystemInfo.IPAddresses.Length - 1);
                    }

                    this.SystemInfo.IPAddresses = this.SystemInfo.IPAddresses.Replace(",,", ",");
                }
            }
            catch (Exception e) {
                Log.WarnFormat("[ThreadID: {0}] {1} DC: {2} Getting Win32_NetworkAdapterConfiguration WMI information: {3}",
                    Thread.CurrentThread.ManagedThreadId, Utility.CurrentMethodName(), this.SystemInfo.DomainController.DNSName, e.VerboseExceptionString());
            }
            #endregion
        } 
        #endregion

    }
}
